/**
 * 自增模型
 *
 * Created by snowingsea on 2020/11/09.
 */
const MongoNumber = require('./mongo/number');
const logger = require('./logger');
const PhoenixError = require('./phoenix-error');

/**
 * 获取任务编号类别名
 * @param {String} userId 用户名
 * @return {string}
 */
function getTaskNumberName(userId) {
  return `${userId}-task`;
}
/**
 *
 * @param {String} functionId
 * @return {String}
 */
function getTestcaseNumberName(functionId) {
  return `${functionId}-testcase`;
}

function getTestcaseTypeNumberName(functionId, typeName) {
  return `${functionId}-type-${typeName}-testcase`;
}
/**
 *
 * @param {String} functionId
 * @return {String}
 */
function getTestcaseGroupNumberName(functionId) {
  return `${functionId}-group-testcase`;
}

function getTestcaseIdentifierNumberName(versionId) {
  return `${versionId}-testcase-identifier`;
}

async function clearDefectNumber(ruleIds) {
  ruleIds = [].concat(ruleIds);
  return MongoNumber.deleteMany({ name: { $in: ruleIds } });
}
/**
 * 获取缺陷编号
 * @param {String} ruleId
 * @return {String}
 */
function getDefectNumberName(ruleId) {
  return `${ruleId}-defect`;
}
/**
 * 获取静态分析文件锁
 * @param {String} fileId
 * @return {String}
 */
function getAnalyzeLockKey(fileId) {
  return `lock-${fileId}-analyze`;
}
/**
 * 获取集成测试文件分析锁
 * @param {String} versionId 版本Id
 * @return {String}
 */
function getIntegrationAnalyzeLockKey(versionId) {
  return `lock-integration-analyze-${versionId}`;
}

function getInvokeGenLockKey(functionId) {
  return `lock-integration-invoke-${functionId}`;
}

function getVersionLockKey(versionId) {
  return `lock-version-${versionId}`;
}

function getCodeInstrumentationNumberName(versionId) {
  return `${versionId}-code-instrumentation`;
}
function getCodeInstrumentationResultsNumberName(versionId, codeInstrumentationId) {
  return `${versionId}-${codeInstrumentationId}-code-instrumentation-result`;
}

/**
 * 初始化编号
 * @param {String} name 类型名
 * @return {Promise}
 */
function initNumber(name) {
  return new Promise((resolve, reject) => {
    const number = new MongoNumber({ name });
    number.save()
      .then(resolve)
      .catch((err) => {
        err.code === 11000 ? resolve() : reject(err);
      });
  });
}

/**
 * 获取下一个编号
 * @param {String} name 类别名
 * @return {Promise}
 */
function next(name) {
  return new Promise((resolve, reject) => {
    let number;
    MongoNumber.findOneAndUpdate({ name }, { $inc: { number: 1 } }, { new: true })
      .then((res) => {
        number = res;
        if (!number) {
          return initNumber(name);
        }
        return Promise.resolve();
      })
      .then(() => {
        if (!number) {
          return next(name);
        }
        return Promise.resolve(number);
      })
      .then(resolve)
      .catch(reject);
  });
}

/**
 * 获取当前编号(只读)
 * @param {String} name 类别名
 * @return {Promise<Number>}
 */
async function current(name) {
  const res = await MongoNumber.findOne({ name });
  if (!res) {
    return 0;
  }
  return res.number;
}

async function restore(name, number = 0) {
  if (number) {
    return MongoNumber.updateOne({ name }, { $set: { number } }, { upsert: true });
  }
  return MongoNumber.deleteOne({ name });
}

/**
 * 释放锁
 * @param {String} name 锁键名
 * @return {Promise}
 */
function releaseLock(name) {
  return MongoNumber.deleteOne({ name });
}

async function waitLock(name, {
  expireSec = 60,
  shouldRetry = true,
  retryInterval = 1000,
  waited = false,
} = {}) {
  const ts = Date.now();
  try {
    await MongoNumber.updateOne({
      name,
      $or: [
        { timestamp: { $lt: ts } },
        { timestamp: { $exists: false } },
      ],
    },
    { timestamp: ts + expireSec * 1000 },
    { upsert: true });
  } catch (error) {
    if (error.name === 'MongoError' && error.code === 11000) {
      if (!shouldRetry) {
        logger.debug(`锁${name}被占`);
        throw new PhoenixError('resource locking');
      }
      // 锁被占, 1s后重试
      await new Promise((res) => {
        setTimeout(() => {
          res();
        }, retryInterval);
      });
      return waitLock(name, { expireSec, waited: true, retryInterval });
    }
    throw error;
  }
  return waited;
}
/**
 * 释放锁
 */
async function removeAllLocks() {
  return MongoNumber.deleteMany({ name: { $regex: /^lock/ } });
}

async function checkLockStatus(name) {
  const lock = await MongoNumber.findOne({
    name,
    timestamp: { $gte: Date.now() },
  });
  if (lock) {
    throw new PhoenixError('resource locking');
  }
}
/**
 * Export model definition object.
 */
module.exports = {
  checkLockStatus,
  clearDefectNumber,
  releaseLock,
  getAnalyzeLockKey,
  getIntegrationAnalyzeLockKey,
  getInvokeGenLockKey,
  getTaskNumberName,
  getVersionLockKey,
  next,
  current,
  restore,
  getTestcaseNumberName,
  getTestcaseGroupNumberName,
  getTestcaseIdentifierNumberName,
  getDefectNumberName,
  waitLock,
  removeAllLocks,
  getCodeInstrumentationNumberName,
  getCodeInstrumentationResultsNumberName,

  getTestcaseTypeNumberName,
};
